// Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
//
// Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
//
// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
//

// File:      Qtx.cxx
// Author:    Sergey TELKOV
//
#include "Qtx.h"

#include <QDir>
#include <QMenu>
#include <QRegExp>
#include <QBitmap>
#include <QWidget>
#include <QLayout>
#include <QPainter>
#include <QDirModel>
#include <QFileInfo>
#include <QCompleter>
#include <QApplication>
#include <QDesktopWidget>
#include <QtDebug>
#include <QSurfaceFormat>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <clocale>

#ifdef WIN32
#include <windows.h>
#define MAX_VALUE_SIZE 32767 // Limit according to http://msdn.microsoft.com/en-us/library/ms683188.aspx
#endif

#include <iostream>

#define BICOLOR_CHANGE_HUE

/*!
  \brief Auxiliary function converting string \a str to the integer value.
  Parameter \a defVal specifies default value that is returned if conversion can't be done.
  Parameters \a minVal and \a maxVal limit the resulting value.
  \param str string being converted
  \param defVal default value
  \param minVal minimum allowed value
  \param maxVal maximum allowed value
  \return integer value obtained from the string
  \internal
*/
static int stringToInt( const QString& str, int defVal, int minVal, int maxVal )
{
  bool ok;
  int v = str.toInt( &ok );
  if ( !ok ) v = defVal;
  return qMin( qMax( v, minVal ), maxVal );
}

/*!
  \class Qtx
  \brief A set of helpful utility functions.

  The class implements a set of the static functions which can be used
  for the different purposes:
  - specify tab order for the set of widgets: setTabOrder()
  - align one widget to the coordinates of the another one: alignWidget()
  - remove extra separators from the menu or toolbar: simplifySeparators()
  - retrieve directory, file name and extension parts of the path:
  dir(), file(), extension()
  - get the path to the temporary directory: tmpDir()
  - create or remove a directory (recursively): mkDir(), rmDir()
  - convert text file from DOS to UNIX native format: dos2unix()
  - convert a picture to the gray scale: grayscale()
  - other
*/

/*!
  \brief Convert character array (ASCII string) to the QString.
  \param str character array
  \param len array length, if < 0, the array should be zero-terminated
  \return QString object
 */
QString Qtx::toQString( const char* str, const int len )
{
  return toQString( (unsigned char*)str, len );
}

/*!
  \brief Convert integer array (UNICODE string) to the QString.
  \param str integer array
  \param len array length, if < 0, the array should be zero-terminated
  \return QString object
 */
QString Qtx::toQString( const short* str, const int len )
{
  return toQString( (unsigned short*)str, len );
}

/*!
  \brief Convert character array (ASCII string) to the QString.
  \param str character array
  \param len array length, if < 0, the array should be zero-terminated
  \return QString object
 */
QString Qtx::toQString( const unsigned char* str, const int len )
{
  QString res;
  const unsigned char* s = str;
  while ( len < 0 || res.length() < len )
  {
    if ( *s == '\0' )
      break;

    res.append( QChar( *s ) );
    s++;
  }
  return res;
}

/*!
  \brief Convert integer array (UNICODE string) to the QString.
  \param str integer array
  \param len array length, if < 0, the array should be zero-terminated
  \return QString object
 */
QString Qtx::toQString( const unsigned short* str, const int len )
{
  QString res;
  const unsigned short* s = str;
  while ( len < 0 || res.length() < len )
  {
    if ( *s == '\0' )
      break;

    res.append( QChar( *s ) );
    s++;
  }
  return res;
}

/*!
  \brief Set tab order for specified list of widgets.

  The function has arbitrary number of parameters, each should be
  hovewer of QWidget* type. Last parameter should be null pointer.

  \param first first widget in the sequence
*/
void Qtx::setTabOrder( QWidget* first, ... )
{
  va_list wids;
  va_start( wids, first );

  QWidgetList widList;

  QWidget* cur = first;
  while ( cur )
  {
    widList.append( cur );
    cur = va_arg( wids, QWidget* );
  }

  setTabOrder( widList );
}

/*!
  \brief Set tab order for specified list of widgets.
  \param widgets list of widgets
*/
void Qtx::setTabOrder( const QWidgetList& widgets )
{
  if ( widgets.count() < 2 )
    return;

  QWidget* prev = 0;
  for ( QWidgetList::const_iterator it = widgets.begin(); it!= widgets.end(); ++it )
  {
    QWidget* next = *it;
    if ( prev && next )
      QWidget::setTabOrder( prev, next );
    prev = next;
  }
}

/*!
  \brief Align widget \a src relative to widget \a ref acording to the 
         alignment flags \a alignFlags.
  \param src source widget (being aligned)
  \param ref reference widget (source widget being aligned to)
  \param alignFlags alignment flags (Qtx::AlignmentFlags)
*/
void Qtx::alignWidget( QWidget* src, const QWidget* ref, const int alignFlags )
{
  if ( !src || !ref || !alignFlags )
    return;

  QPoint srcOri = src->pos();
  QPoint refOri = ref->pos();
  if ( src->parentWidget() && !src->isTopLevel() )
    srcOri = src->parentWidget()->mapToGlobal( srcOri );
  if ( ref->parentWidget() && !ref->isTopLevel() )
    refOri = ref->parentWidget()->mapToGlobal( refOri );

  int x = srcOri.x(), y = srcOri.y();
  int refWidth = ref->frameGeometry().width(), refHei = ref->frameGeometry().height();
  int srcWidth = src->frameGeometry().width(), srcHei = src->frameGeometry().height();

  if ( srcWidth <= 0 )
    srcWidth = src->sizeHint().width();
  if ( srcHei <= 0 )
    srcHei = src->sizeHint().height();

  int border = 0;
  if ( ref->isTopLevel() && ref->isMaximized() &&
       src->isTopLevel() && !src->isMaximized() )
    border = ( src->frameGeometry().width() - src->width() ) / 2;

  if ( alignFlags & Qtx::AlignLeft )
    x = refOri.x() + border;
  if ( alignFlags & Qtx::AlignOutLeft )
    x = refOri.x() - srcWidth - border;
  if ( alignFlags & Qtx::AlignRight )
    x = refOri.x() + refWidth - srcWidth - border;
  if ( alignFlags & Qtx::AlignOutRight )
    x = refOri.x() + refWidth + border;
  if ( alignFlags & Qtx::AlignTop )
    y = refOri.y() + border;
  if ( alignFlags & Qtx::AlignOutTop )
    y = refOri.y() - srcHei - border;
  if ( alignFlags & Qtx::AlignBottom )
    y = refOri.y() + refHei - srcHei - border;
  if ( alignFlags & Qtx::AlignOutBottom )
    y = refOri.y() + refHei + border;
  if ( alignFlags & Qtx::AlignHCenter )
    x = refOri.x() + ( refWidth - srcWidth ) / 2;
  if ( alignFlags & Qtx::AlignVCenter )
    y = refOri.y() + ( refHei - srcHei ) / 2;

  if ( src->parentWidget() && !src->isTopLevel() )
  {
    QPoint pos = src->parentWidget()->mapFromGlobal( QPoint( x, y ) );
    x = pos.x();
    y = pos.y();
  }

  QWidget* desk = QApplication::desktop();
  if ( desk && x + srcWidth + border > desk->width() )
    x = desk->width() - srcWidth - border;
  if ( desk && y + srcHei + border > desk->height() )
    y = desk->height() - srcHei - border;

  x = qMax( x, 0 );
  y = qMax( y, 0 );

  src->move( x, y );
}

/*!
  \brief Remove (recursively) unnecessary separators from the menu or toolbar.
  \param wid widget, should be of QMenu* or QToolBar* class
*/
void Qtx::simplifySeparators( QWidget* wid, const bool recursive )
{
  if ( !wid )
    return;

  if ( wid->inherits( "QMenu") || wid->inherits( "QMenuBar") )
  {
    if ( qobject_cast<QMenu*>( wid ) )
      qobject_cast<QMenu*>( wid )->setSeparatorsCollapsible( true );
    if ( recursive )
    {
      foreach ( QAction* action, wid->actions() )
      {
	if ( action->menu() )
	  simplifySeparators( action->menu(), recursive );
      }
    }
  }
  else
  {
    QList<QAction*> actions = wid->actions();
    if ( actions.isEmpty() )
      return;

    bool is_action = false;
    for ( int i = 0; i < actions.count(); i++ )
    {
      QAction* action = actions[i];
      if ( action->isSeparator() )
      {
	action->setVisible( is_action );
	is_action = false;
      }
      else if ( action->isVisible() )
      {
	is_action = true;
      }
    }
    is_action = false;
    for ( int i = actions.count() - 1; i > 0; i-- )
    {
      QAction* action = actions[i];
      if ( action->isSeparator() )
      {
	action->setVisible( is_action );
	is_action = false;
      }
      else if ( action->isVisible() )
      {
	is_action = true;
      }
    }
  }
}

/*!
  \brief Return \c true if specified \a parent is a parent object
         of given \a child (in terms of QObject).

  This function works recursively. It means that \a true is also
  returned if \a parent is a grand-father, grand-grand-father, etc
  of \a child. If the same object is given as both \a parent and
  \a child, \c true is also returned.

  \param child child object
  \param parent parent object
  \return \c true if the \a parent is a parent of \a child
*/
bool Qtx::isParent( QObject* child, QObject* parent )
{
  if ( !child || !parent )
    return false;

  bool res = false;
  QObject* obj = child;
  while ( !res && obj )
  {
    res = obj == parent;
    obj = obj->parent();
  }
  return res;
}

/*!
  \brief Find the parent object of class specified by \a className (in terms of QObject).

  \param obj current object
  \param className class name of the parent
  \return parent object or null pointer if the parent not found
*/
QObject* Qtx::findParent( QObject* obj, const char* className )
{
  if ( !obj )
    return 0;

  if ( !className || !strlen( className ) )
    return obj->parent();

  QObject* res = 0;
  QObject* p = obj->parent();
  while ( p && !res )
  {
    if ( p->inherits( className ) )
      res = p;
    p = p->parent();
  }

  return res;
}

/*!
  \brief Return directory part of the file path.

  If the file path does not include directory part (the file is in the
  current directory), null string is returned.

  \param path file path
  \param abs if true (default) \a path parameter is treated as absolute file path
  \return directory part of the file path
*/
QString Qtx::dir( const QString& path, const bool abs )
{
  QDir aDir = QFileInfo( path ).dir();
  QString dirPath = abs ? aDir.absolutePath() : aDir.path();
  if ( dirPath == QString( "." ) )
    dirPath = QString();
  return dirPath;
}

/*!
  \brief Return file name part of the file path.

  \param path file path
  \param withExt if true (default) complete file name (with all
         extension except the last) is returned, otherwise only base name
         is returned
  \return file name part of the file path
*/
QString Qtx::file( const QString& path, bool withExt )
{
  QString fPath = path;
  while ( !fPath.isEmpty() && ( fPath[fPath.length() - 1] == '\\' || fPath[fPath.length() - 1] == '/' ) )
    fPath.remove( fPath.length() - 1, 1 );

  if ( withExt )
    return QFileInfo( fPath ).fileName();
  else
    return QFileInfo( fPath ).completeBaseName();
}

/*!
  \brief Return extension part of the file path.

  \param path file path
  \param full if true complete extension (all extensions, dot separated)
         is returned, otherwise (default) only last extension is returned
  \return extension part of the file path
*/
QString Qtx::extension( const QString& path, const bool full )
{
  return full ? QFileInfo( path ).completeSuffix() : QFileInfo( path ).suffix();
}

/*!
  \brief Convert the given parameter to the platform-specific library name.

  The function appends platform-specific prefix (lib) and suffix (.dll/.so)
  to the library file name.
  For example, if \a str = "mylib", "libmylib.so" is returned for Linux and
  mylib.dll for Windows.

  \param str short library name
  \return full library name
*/
QString Qtx::library( const QString& str )
{
  QString path = dir( str, false );
  QString name = file( str, false );
  QString ext  = extension( str );

#ifndef WIN32
  if ( !name.startsWith( "lib" ) )
    name = QString( "lib" ) + name;
#endif

#if defined(WIN32)
  QString libExt( "dll" );
#elif defined(__APPLE__)
  QString libExt( "dylib" );
#else
  QString libExt( "so" );
#endif

  if ( ext.toLower() != QString( "so" ) && ext.toLower() != QString( "dll" ) && ext.toLower() != QString( "dylib" ) )
  {
    if ( !name.isEmpty() && !ext.isEmpty() )
      name += QString( "." );
    name += ext;
  }

  ext = libExt;

  QString fileName = addSlash( path ) + name + QString( "." ) + ext;

  return fileName;
}

/*!
  \brief Get the temporary directory name.
  \return temporary directory (platform specific)
*/
QString Qtx::tmpDir()
{
  QString tmpdir = getenv( "TEMP" );
  if ( tmpdir.isEmpty() )
    tmpdir = getenv ( "TMP" );
  if ( tmpdir.isEmpty() )
  {
#ifdef WIN32
    tmpdir = QString("C:\\");
#else
    tmpdir = QString("/tmp");
#endif
  }
  return tmpdir;
}

/*!
  \brief Create directory recursively including all intermediate sub directories.
  \return \c true if the directory is successfully created and \c false otherwise
*/
bool Qtx::mkDir( const QString& dirPath )
{
  return QDir().mkpath( dirPath );
}

/*!
  \brief Remove directory recursively including all subdirectories and files.
  \return \c true if the directory is successfully removed and \c false otherwise
*/
bool Qtx::rmDir( const QString& thePath )
{
  QFileInfo fi( thePath );
  if ( !fi.exists() )
    return true;

  bool stat = true;
  if ( fi.isFile() )
    stat = QFile::remove( thePath );
  else if ( fi.isDir() )
  {
    QDir aDir( thePath );
    QFileInfoList anEntries = aDir.entryInfoList();
    for ( QFileInfoList::iterator it = anEntries.begin(); it != anEntries.end(); ++it )
    {
      QFileInfo inf = *it;
      if ( inf.fileName() == "." || inf.fileName() == ".." )
        continue;
      stat = stat && rmDir( inf.absoluteFilePath() );
    }
    stat = stat && aDir.rmdir( thePath );
  }
  return stat;
}

/*!
  \brief Add a slash (platform-specific) to the end of \a path
         if it is not already there.
  \param path directory path
  \return modified path (with slash added to the end)
*/
QString Qtx::addSlash( const QString& path )
{
  QString res = path;
  if ( !res.isEmpty() && res.at( res.length() - 1 ) != QChar( '/' ) &&
       res.at( res.length() - 1 ) != QChar( '\\' ) )
  res += QDir::separator();
  return res;
}

/*!
  \brief Convert text file from DOS format to UNIX.

  The function replaces "LF/CR" symbols sequence by "LF" symbol.

  \param absName file name
  \return \c true if the file is converted successfully and \c false in
         case of any error
*/
bool Qtx::dos2unix( const QString& absName )
{
  FILE* src = ::fopen( absName.toUtf8(), "rb" );
  if ( !src )
    return false;

  /* we'll use temporary file */
  char temp[512] = { '\0' };
  QString dir = Qtx::dir( absName );
  FILE* tgt = ::fopen( strcpy( temp, ::tempnam( dir.toUtf8(), "__x" ) ), "wb" );
  if ( !tgt )
    return false;

  /* temp -> result of conversion */
  const char CR = 0x0d;
  const char LF = 0x0a;
  bool waitingLF = false;

  while( true )
  {
    int  outcnt = 0;
    char inbuf[512], outbuf[512];

    /* convert buffer */
    int nbread = (int)::fread( inbuf, 1, sizeof( inbuf ), src ); //!< TODO: conversion from 'size_t' to 'int'
    for ( int incnt = 0; incnt < nbread; incnt++  )
    {
      if ( waitingLF )
      {
        waitingLF = false;
        if ( inbuf[incnt] == LF )
          outbuf[outcnt++] = LF;
        else
          outbuf[outcnt++] = CR;
      }
      else if ( inbuf[incnt] == CR )
        waitingLF = true;
      else
        outbuf[outcnt++] = inbuf[incnt];
    }

    /* check last sym in buffer */
    waitingLF = ( inbuf[nbread - 1] == CR );

    /* write converted buffer to temp file */
    int nbwri = (int)::fwrite( outbuf, 1, outcnt, tgt ); //!< TODO: conversion from 'size_t' to 'int'
    if ( nbwri != outcnt )
    {
      ::fclose( src );
                        ::fclose( tgt );
      QFile::remove( QString( temp ) );
      return false;
    }
    if ( nbread != sizeof( inbuf ) )
      break;              /* converted ok */
  }
  ::fclose( src );
  ::fclose( tgt );

  /* rename temp -> src */
  if ( !QFile::remove( absName ) )
    return false;

  return QDir().rename( QString( temp ), absName );
}

/*!
  \brief Create path completer which can be used in the widgets
  to provide auto completions.

  Create an instance of QCompleter class and returns the pointer on it.
  The calling function is responsible to the desstroying of the created 
  completer object.

  The QCompleter class provides completions based on a item model and can be
  used in such as QLineEdit and QComboBox. 
  When the user starts typing a word, QCompleter suggests possible ways of 
  completing the word, based on a word list. 

  \param type path type (Qtx::PathType)
  \param filter file/directory filters (list of wildcards, separated by ";;")
  \return a pointer to the created completer
*/
QCompleter* Qtx::pathCompleter( const PathType type, const QString& filter )
{
  QStringList extList;
  QStringList filterList = filter.split( ";;" );
  for ( QStringList::const_iterator it = filterList.begin(); it != filterList.end(); ++it )
  {
    QRegExp rx( "[\\s\\w,;]*\\(?\\*\\.([\\w]+)\\)?[\\d\\s\\w]*" );
    int index = 0;
    while ( ( index = rx.indexIn( *it, index ) ) != -1 )
    {
      extList.append( QString( "*.%1" ).arg( rx.cap( 1 ) ) );
      index += rx.matchedLength();
    }
  }

  QDir::Filters filters = 0;
  switch ( type )
  {
  case PT_OpenFile:
  case PT_SaveFile:
    filters = QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot;
    break;
  case PT_Directory:
    filters = QDir::Drives | QDir::Dirs | QDir::NoDotAndDotDot;
    break;
  }

  QDirModel* dm = new QDirModel( extList, filters, QDir::Unsorted );
  QCompleter* cmp = new QCompleter( dm, 0 );
  dm->setParent( cmp );

  return cmp;
}

/*!
  \brief Parse given string to retrieve environment variable.

  Looks through the string for the environment variable patterns.
  If string contains variable satisfying any pattern, the variable name
  is returned, start index of the variable is returned in the \a start parameter,
  and length of the variable is returned in the \a len parameter.

  Supported environment variables definitions:
  - ${name} or $name : Linux shell variable
  - $(name)          : GNU make substitution
  - %name%           : Windows shell variable
  - %(name)s         : Python substitutions:

  \param str string being processed
  \param start if variable is found, this parameter contains its starting 
         position in the \a str
  \param len if variable is found, this parameter contains its length 
  \return first found variable or null QString if there is no ones
*/
QString Qtx::findEnvVar( const QString& str, int& start, int& len )
{
  QString varName;
  len = 0;

  QStringList rxList;
  rxList << "\\$\\{([a-zA-Z][a-zA-Z_0-9]*)\\}"; // ${name}
  rxList << "\\$([a-zA-Z][a-zA-Z_0-9]*)";       // $name
  rxList << "\\$\\(([a-zA-Z][a-zA-Z_0-9]*)\\)"; // $(name)
  rxList << "%([a-zA-Z][a-zA-Z0-9_]*)%";        // %name%
  rxList << "%\\(([a-zA-Z][a-zA-Z_0-9]*)\\)s";  // %(name)s
  
  for ( int i = 0; i < rxList.count() && varName.isEmpty(); ++i ) 
  {
    QRegExp rx(rxList[i]);
    int pos = rx.indexIn( str, start );
    if ( pos != -1 )
    {
      varName = rx.cap( 1 );
      start = pos;
      len = rx.matchedLength();
    }
  }
  return varName;
}

/*!
  \brief Substitute environment variables by their values.

  Environment variable is substituted by its value.

  \param str string to be processed
  \return processed string (with all substitutions made)
*/
QString Qtx::makeEnvVarSubst( const QString& str, const SubstMode mode )
{
  QString res = str;
  if ( mode != Never )
  {
    QMap<QString, int> ignoreMap;

    int start( 0 ), len( 0 );
    while ( true )
    {
      QString envName = findEnvVar( res, start, len );
      if ( envName.isNull() )
        break;

      QString newStr;
      if ( getenv( envName ).isEmpty() || mode == Always )
        newStr = QString( getenv( envName ) );

      if ( newStr.isNull() )
      {
        if ( ignoreMap.contains( envName ) )
        {
          start += len;
          continue;
        }
        ignoreMap.insert( envName, 0 );
      }
      res.replace( start, len, newStr );
    }

    res.replace( "$$", "$" );
    res.replace( "%%", "%" );
  }

  return res;
}

/*!
  \brief Pack the specified color into integer RGB set.
  \param c unpacked color
  \return packed color
*/
int Qtx::rgbSet( const QColor& c )
{
  return rgbSet( c.red(), c.green(), c.blue() );
}

/*!
  \brief Pack the specified RGB color components into integer RGB set.
  \param r red component
  \param g green component
  \param b blue component
  \return packed color
*/
int Qtx::rgbSet( const int r, const int g, const int b )
{
  return ( ( ( 0xff & r ) << 16 ) + ( ( 0xff & g ) << 8 ) + ( 0xff & b ) );
}

/*!
  \brief Unpack the specified integer RGB set to the color.
  \param rgb packed color
  \return unpacked color (QColor)
*/
QColor Qtx::rgbSet( const int rgb )
{
  int r, g, b;
  rgbSet( rgb, r, g, b );
  return QColor( r, g, b );
}

/*!
  \brief Unpack the specified integer RGB set to the three RGB components.
  \param rgb packed color
  \param r returned unpacked red component
  \param g returned unpacked green component
  \param b returned unpacked blue component
*/
void Qtx::rgbSet( const int rgb, int& r, int& g, int& b )
{
  r = ( rgb >> 16 ) & 0xff;
  g = ( rgb >> 8 ) & 0xff;
  b = rgb & 0xff;
}

/*!
  \brief Return the color specified by the index between min (blue) and max (red).
  \param index color index
  \param min required minimum hue value
  \param max required maximum hue value
  \return resulting color
*/
QColor Qtx::scaleColor( const int index, const int min, const int max )
{
  static const int HUE[10] = {230, 210, 195, 180, 160, 80, 60, 50, 30, 0};

  int hue = HUE[0];

  if ( min != max )
  {
    double aPosition = 9.0 * ( index - min ) / ( max - min );
    if ( aPosition > 0.0 )
    {
      if ( aPosition >= 9.0 )
        hue = HUE[9];
      else
      {
        int idx = (int)aPosition;
        hue = HUE[idx] + int( ( aPosition - idx ) * ( HUE[idx + 1] - HUE[idx] ) );
      }
    }
  }

  return QColor::fromHsv( hue, 255, 255 );
}

/*!
  \brief Generate required number of colors aligned from blue to red.
  \param num required number of colors
  \param lst returned set of colors
*/
void Qtx::scaleColors( const int num, QColorList& lst )
{
  lst.clear();
  for ( int i = 0; i < num; i++ )
    lst.append( scaleColor( i, 0, num - 1 ) );
}

/*!
  \brief Scale the pixmap to the required size.

  If \a h is 0 (default) the value of \a w is used instead (to create
  square pixmap).

  \param icon pixmap to be resized
  \param w required pixmap width
  \param h required pixmap height
  \return scaled pixmap
*/
QPixmap Qtx::scaleIcon( const QPixmap& icon, const unsigned w, const unsigned h )
{
  QPixmap p;
  int aw = w, ah = h <= 0 ? w : h;
  if ( icon.isNull() || aw <= 0 || ah <= 0 || ( aw == icon.width() && ah == icon.height() ) )
    p = icon;
  else
    p = icon.fromImage( icon.toImage().scaled( aw, ah, Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
  return p;
}

/*!
  \brief Convert given image to the grayscale format.
  \param img initial image
  \return converted to the grayscale image
*/
QImage Qtx::grayscale( const QImage& img )
{
  QImage res = img;

  int colNum = res.colorCount();
  if ( colNum )
  {
    for ( int i = 0; i < colNum; i++ )
      res.setColor( i, qGray( res.color( i ) ) );
  }
  else
  {
    for ( int y = 0; y < res.height(); y++ )
    {
      for ( int x = 0; x < res.width(); x++ )
      {
        QRgb pix = res.pixel( x, y );
        res.setPixel( x, y, qRgba( qGray( pix ), qGray( pix ), qGray( pix ), qAlpha( pix ) ) );
      }
    }
  }

  return res;
}

/*!
  \brief Convert given pixmap to the grayscale format.
  \param pix initial pixmap
  \return converted to the grayscale pixmap
*/
QPixmap Qtx::grayscale( const QPixmap& pix )
{
  QPixmap res;
  res.fromImage( grayscale( pix.toImage() ) );
  return res;
}

/*!
  \brief Create transparent image.
  \param w required image width
  \param h required image height
  \param d required image depth
  \return generated image
*/
QImage Qtx::transparentImage( const int w, const int h, const int d )
{
  QImage::Format fmt;
  switch ( d )
  {
  case 1:
    fmt = QImage::Format_Mono;
    break;
  case 8:
    fmt = QImage::Format_Indexed8;
    break;
  case 16:
  case 24:
  case 32:
  default:
    fmt = QImage::Format_ARGB32;
    break;
  }

  QImage img( w, h, fmt );
  if ( !img.isNull() )
  {
//    img.setAlphaBuffer( true );
    for ( int i = 0; i < img.height(); i++ )
      for ( int j = 0; j < img.width(); j++ )
        img.setPixel( j, i, qRgba( 0, 0, 0, 0 ) );
  }
  return img;
}

/*!
  \brief Create transparent pixmap.
  \param w required image width
  \param h required pixmap height
  \param d required pixmap depth
  \return generated pixmap
*/
QPixmap Qtx::transparentPixmap( const int w, const int h, const int d )
{
  QPixmap pix;
  QImage img = transparentImage( w, h, d );
  if ( !img.isNull() )
    pix.fromImage( img );
  return pix;
}

/*!
  \brief Create composite pixmap. 

  Pixmap \a pix is drawn over pixmap \a dest with coordinates
  specified relatively to the upper left corner of \a dest.
  If \a dest is not given, the new empty pixmap with appropriate size created instead.

  \param pix source pixmap
  \param x horizontal shift
  \param y vertical shift
  \param dest background pixmap
  \return resulting pixmap
*/
QPixmap Qtx::composite( const QPixmap& pix, const int x, const int y, const QPixmap& dest )
{
  if ( pix.isNull() )
    return dest;

  int width = qMax( pix.width() + x, dest.width() );
  int height = qMax( pix.height() + y, dest.height() );

  QPixmap res( width, height );
  QImage img = transparentImage( width, height, 32 );

  QPainter p;
  p.begin( &res );
  p.fillRect( 0, 0, width, height, QBrush( Qt::white ) );

  if ( !dest.isNull() )
  {
    p.drawPixmap( 0, 0, dest );
    QImage temp = dest.toImage();
    for ( int i = 0; i < temp.width() && i < img.width(); i++ )
    {
      for ( int j = 0; j < temp.height() && j < img.height(); j++ )
      {
        if ( temp.hasAlphaChannel() )
          img.setPixel( i, j, temp.pixel( i, j ) );
        else
        {
          QRgb p = temp.pixel( i, j );
          img.setPixel( i, j, qRgba( qRed( p ), qGreen( p ), qBlue( p ), 255 ) );
        }
      }
    }
  }

  p.drawPixmap( x, y, pix );
  QImage temp = pix.toImage();
  for ( int c = x; c < temp.width() + x && c < img.width(); c++ )
  {
    for ( int r = y; r < temp.height() + y && r < img.height(); r++ )
    {
      if ( qAlpha( temp.pixel( c - x, r - y ) ) > 0 )
        img.setPixel( c, r, temp.pixel( c - x, r - y ) );
    }
  }

  p.end();

  for ( int ai = 0; ai < img.width(); ai++ )
  {
    for ( int aj = 0; aj < img.height(); aj++ )
    {
      if ( qAlpha( img.pixel( ai, aj ) ) < 1 )
        img.setPixel( ai, aj, qRgba( 255, 255, 255, 255 ) );
      else
        img.setPixel( ai, aj, qRgba( 0, 0, 0, 0 ) );
    }
  }

  QBitmap bmp( width, height );
  bmp.fromImage( img, Qt::ColorMode_Mask | Qt::ThresholdDither );
  res.setMask( bmp );

  return res;
}

/*!
  \brief Convert color to the string representation.
  
  The resulting string is in the one of two possible formats
  (\c RR, \c GG, \c BB and \c AA value represent red, green, blue
  and alpha components of the color):
  - if color has alpha channel : "#RR,#GG,#BB,#AA"
  - if color does not have alpha channel : "#RRGGBB" 

  If color is invalid, null string is returned.

  Backward conversion can be done with stringToColor() method.

  \param color color to be converted
  \return string representation of the color

  \sa stringToColor()
*/
QString Qtx::colorToString( const QColor& color )
{
  QString str;
  if ( color.isValid() )
  {
    if ( color.alpha() != 255 )
    {
      QStringList vals;
      vals << QString( "#%1" ).arg( color.red(),   0, 16 );
      vals << QString( "#%1" ).arg( color.green(), 0, 16 );
      vals << QString( "#%1" ).arg( color.blue(),  0, 16 );
      vals << QString( "#%1" ).arg( color.alpha(), 0, 16 );
      str = vals.join( "," );
    }
    else
    {
      str = color.name();
    }
  }
  return str;
}

/*!
  \brief Create color from the string representation.
  
  The parameter \a str must be in the one of following formats
  (\c RR, \c GG, \c BB and \c AA value represent red, green, blue
  and alpha components of the color):
  - "#RR,#GG,#BB[,#AA]" or "#RR #GG #BB[ #AA]" (\c RR, \c GG, \c BB
  and optional \c AA values represent red, green, blue and alpha
  components of the color in hexadecimal form)
  - "RR,GG,BB[,AA]" or "RR GG BB[ AA]" (\c RR, \c GG, \c BB
  and optional \c AA values represent red, green, blue and alpha
  components of the color in decimal form)
  - "#RRGGBB" - (\c RR, \c GG and \c BB values represent red, green and blue
  components of the color in hexadecimal form)
  - an integer value representing packed color components (see rgbSet())
  - a name from the list of colors defined in the list of SVG color keyword names
  provided by the World Wide Web Consortium; for example, "steelblue" or "gainsboro".

  Backward conversion can be done with colorToString() method.

  \param str string representation of the color
  \param color resulting color value
  \return \c true if the conversion is successful and \c false otherwise

  \sa colorToString(), rgbSet()
*/
bool Qtx::stringToColor( const QString& str, QColor& color )
{
  bool res = true;
  QStringList vals = str.split( QRegExp( "[\\s|,]" ), QString::SkipEmptyParts );

  QIntList nums;
  for ( QStringList::const_iterator it = vals.begin(); it != vals.end() && res; ++it )
  {
    int num = 0;
    if ( (*it).startsWith( "#" ) )
      num = (*it).mid( 1 ).toInt( &res, 16 );
    else
      num = (*it).toInt( &res, 10 );
    if ( res )
      nums.append( num );
  }

  res = res && nums.count() >= 3;
  if ( res )
    color.setRgb( nums[0], nums[1], nums[2] );

  if ( !res )
  {
    int pack = str.toInt( &res );
    if ( res )
      color = Qtx::rgbSet( pack );
  }

  if ( !res )
  {
    color = QColor( str );
    res = color.isValid();
  }

  return res;
}

/*!
  \brief Convert bi-color value to the string representation.
  
  Bi-color value is specified as main color and integer delta
  value that is used to calculate secondary color by changing
  paremeters of the main color ("saturation" and "value"
  components in HSV notation).

  The resulting string consists of two sub-strings separated by
  '|' symbol. The first part represents main color
  (see colorToString() for more details), the second part is a
  delta value.

  Backward conversion can be done with stringToBiColor() method.

  \param color color to be converted
  \param delta delta value
  \return string representation of the bi-color value

  \sa stringToBiColor(), stringToColor()
*/
QString Qtx::biColorToString( const QColor& color, const int delta )
{
  return QString("%1|%2").arg( Qtx::colorToString( color ) ).arg( delta );
}

/*!
  \brief Restore bi-color value from the string representation.

  Bi-color value is specified as main color and integer delta
  value that is used to calculate secondary color by changing
  paremeters of the main color ("saturation" and "value"
  components in HSV notation).

  The parameter \a str should consist of two sub-strings separated
  by '|' symbol. The first part represents main color
  (see stringToColor() for more details), the second part is a
  delta value.

  Backward conversion can be done with biColorToString() method.

  \param str string representation of the bi-color value
  \param color resulting color value
  \param delta resulting delta value
  \return \c true if the conversion is successful and \c false otherwise

  \sa biColorToString(), stringToColor(), rgbSet()
*/
bool Qtx::stringToBiColor( const QString& str, QColor& color, int& delta )
{
  QStringList data = str.split( "|", QString::KeepEmptyParts );
  QColor c;
  int d = 0;
  bool ok = data.count() > 0 && Qtx::stringToColor( data[0], c );
  bool dok = false;
  if ( data.count() > 1 ) d = data[1].toInt( &dok );
  ok = ok && dok;
  color = ok ? c : QColor();
  delta = ok ? d : 0;
  return ok;
}

/*!
  \brief Compute secondary color value from specified main color
  and delta.

  Secondary color is calculated by changing paremeters of the main
  color ("saturation" and "value" components in HSV notation) using
  specified delta.

  If main color is invalid, result of the function is also invalid color.

  \param color source main color
  \param delta delta value
  \return resulting secondary color
  
  \sa biColorToString(), stringToBiColor()
*/
QColor Qtx::mainColorToSecondary( const QColor& color, int delta )
{
  QColor cs = color;
  if ( cs.isValid() ) {
    int val = qMin( 255, qMax( cs.value() + delta, 0 ) );
    int sat = qMin( 255, qMax( cs.saturation() + delta-(val-cs.value()), 0 ) );
#ifdef BICOLOR_CHANGE_HUE
    const int BICOLOR_HUE_MAXDELTA = 40;
    int dh = delta-(val-cs.value())-(sat-cs.saturation());
    dh = qMin( BICOLOR_HUE_MAXDELTA, qAbs( dh ) ) * ( dh > 0 ? 1 : -1 );
    //int hue = qMin( 359, qMax( cs.hue() + delta-(val-cs.value())-(sat-cs.saturation()), 0 ) );
    //int hue = qMin( 359, qMax( cs.hue() + delta-(val-cs.value())-ds, 0 ) );
    int hue = cs.hue() + dh;
    if ( hue < 0 ) hue = 360 - hue;
#else
    int hue = cs.hue();
#endif
    cs.setHsv( hue, sat, val );
  }
  return cs;
}

/*!
  \brief Dump linear gradient to the string description.
  \param gradient linear gradient to be converted
  \return string representation of the linear gradient
  \sa stringToLinearGradient()
*/
QString Qtx::gradientToString( const QLinearGradient& gradient )
{
  QStringList data;
  data << "linear";
  data << QString::number( gradient.start().x() );
  data << QString::number( gradient.start().y() );
  data << QString::number( gradient.finalStop().x() );
  data << QString::number( gradient.finalStop().y() );
  switch( gradient.spread() ) 
  {
  case QGradient::PadSpread:
    data << "pad";
    break;
  case QGradient::RepeatSpread:
    data << "repeat";
    break;
  case QGradient::ReflectSpread:
    data << "reflect";
    break;
  default:
    break;
  }
  QGradientStops stops = gradient.stops();
  QGradientStop stop;
  foreach ( stop, stops ) 
  {
    data << QString::number( stop.first );
    data << colorToString( stop.second );
  }
  return data.join( "|" );
}

/*!
  \brief Dump radial gradient to the string description.
  \param gradient radial gradient to be converted
  \return string representation of the radial gradient
  \sa stringToRadialGradient()
*/
QString Qtx::gradientToString( const QRadialGradient& gradient )
{
  QStringList data;
  data << "radial";
  data << QString::number( gradient.center().x() );
  data << QString::number( gradient.center().y() );
  data << QString::number( gradient.focalPoint().x() );
  data << QString::number( gradient.focalPoint().y() );
  data << QString::number( gradient.radius() );
  switch( gradient.spread() ) 
  {
  case QGradient::PadSpread:
    data << "pad";
    break;
  case QGradient::RepeatSpread:
    data << "repeat";
    break;
  case QGradient::ReflectSpread:
    data << "reflect";
    break;
  default:
    break;
  }
  QGradientStops stops = gradient.stops();
  QGradientStop stop;
  foreach ( stop, stops ) 
  {
    data << QString::number( stop.first );
    data << colorToString( stop.second );
  }
  return data.join( "|" );
}

/*!
  \brief Dump conical gradient to the string description.
  \param gradient conical gradient to be converted
  \return string representation of the conical gradient
  \sa stringToConicalGradient()
*/
QString Qtx::gradientToString( const QConicalGradient& gradient )
{
  QStringList data;
  data << "conical";
  data << QString::number( gradient.center().x() );
  data << QString::number( gradient.center().y() );
  data << QString::number( gradient.angle() );
  switch( gradient.spread() ) 
  {
  case QGradient::PadSpread:
    data << "pad";
    break;
  case QGradient::RepeatSpread:
    data << "repeat";
    break;
  case QGradient::ReflectSpread:
    data << "reflect";
    break;
  default:
    break;
  }
  QGradientStops stops = gradient.stops();
  QGradientStop stop;
  foreach ( stop, stops ) 
  {
    data << QString::number( stop.first );
    data << colorToString( stop.second );
  }
  return data.join( "|" );
}

/*!
  \brief Create linear gradient from its string representation.
  \param str string representation of the linear gradient
  \param gradient resulting linear gradient object
  \return \c true if the conversion is successful and \c false otherwise
  \sa gradientToString()
*/
bool Qtx::stringToLinearGradient( const QString& str, QLinearGradient& gradient )
{
  bool success = false;
  QStringList vals = str.split( "|", QString::SkipEmptyParts );
  if ( vals.count() > 4 && ( vals[0] == "linear" || vals[0] == "lg" ) )
  {
    // start and end points 
    double x1, y1, x2, y2;
    bool bOk1, bOk2, bOk3, bOk4;
    x1 = vals[1].toDouble( &bOk1 );
    y1 = vals[2].toDouble( &bOk2 );
    x2 = vals[3].toDouble( &bOk3 );
    y2 = vals[4].toDouble( &bOk4 );
    if ( bOk1 && bOk2 && bOk3 && bOk4 )
    {
      gradient = QLinearGradient( x1, y1, x2, y2 );
      // spread type
      if ( vals.count() > 5 )
      {
        QString spread = vals[ 5 ].trimmed().toLower();
        if ( spread == "pad" || spread == "0" )
          gradient.setSpread( QGradient::PadSpread );
        else if ( spread == "repeat" || spread == "2" )
          gradient.setSpread( QGradient::RepeatSpread );
        else if ( spread == "reflect" || spread == "1" )
          gradient.setSpread( QGradient::ReflectSpread );
      }
      // stop points
      QGradientStops stops;
      for ( int i = 6; i < vals.count(); i+=2 )
      {
        bool bOk5, bOk6 = false;
        QColor c;
        double stop = vals[i].toDouble( &bOk5 );
        if ( i+1 < vals.count() )
          bOk6 = stringToColor( vals[ i+1 ], c );
        if ( bOk5 && stop >= 0.0 && stop <= 1.0 && bOk6 && c.isValid() )
          stops.append( QGradientStop( stop, c ) );
      }
      gradient.setStops( stops );
      success = true;
    }
  }
  return success;
}

/*!
  \brief Create radial gradient from its string representation.
  \param str string representation of the radial gradient
  \param gradient resulting radial gradient object
  \return \c true if the conversion is successful and \c false otherwise
  \sa gradientToString()
*/
bool Qtx::stringToRadialGradient( const QString& str, QRadialGradient& gradient )
{
  bool success = false;
  QStringList vals = str.split( "|", QString::SkipEmptyParts );
  if ( vals.count() > 5 && ( vals[0] == "radial" || vals[0] == "rg" ) )
  {
    // center, radius and focal point
    double cx, cy, r, fx, fy;
    bool bOk1, bOk2, bOk3, bOk4, bOk5;
    cx = vals[1].toDouble( &bOk1 );
    cy = vals[2].toDouble( &bOk2 );
    fx = vals[3].toDouble( &bOk4 );
    fy = vals[4].toDouble( &bOk5 );
    r  = vals[5].toDouble( &bOk3 );
    if ( bOk1 && bOk2 && bOk3 && bOk4 && bOk5 )
    {
      gradient = QRadialGradient( cx, cy, r, fx, fy );
      // spread type
      if ( vals.count() > 6 )
      {
        QString spread = vals[ 6 ].trimmed().toLower();
        if ( spread == "pad" || spread == "0" )
          gradient.setSpread( QGradient::PadSpread );
        else if ( spread == "repeat" || spread == "2" )
          gradient.setSpread( QGradient::RepeatSpread );
        else if ( spread == "reflect" || spread == "1" )
          gradient.setSpread( QGradient::ReflectSpread );
      }
      // stop points
      QGradientStops stops;
      for ( int i = 7; i < vals.count(); i+=2 )
      {
        bool bOk7, bOk8 = false;
        QColor c;
        double stop = vals[i].toDouble( &bOk7 );
        if ( i+1 < vals.count() )
          bOk8 = stringToColor( vals[ i+1 ], c );
        if ( bOk7 && stop >= 0.0 && stop <= 1.0 && bOk8 && c.isValid() )
          stops.append( QGradientStop( stop, c ) );
      }
      gradient.setStops( stops );
      success = true;
    }
  }
  return success;
}

/*!
  \brief Create conical gradient from its string representation.
  \param str string representation of the conical gradient
  \param gradient resulting conical gradient object
  \return \c true if the conversion is successful and \c false otherwise
  \sa gradientToString()
*/
bool Qtx::stringToConicalGradient( const QString& str, QConicalGradient& gradient )
{
  bool success = false;
  QStringList vals = str.split( "|", QString::SkipEmptyParts );
  if ( vals.count() > 3 && ( vals[0] == "conical" || vals[0] == "cg" ) )
  {
    // center and angle
    double cx, cy, a;
    bool bOk1, bOk2, bOk3;
    cx = vals[1].toDouble( &bOk1 );
    cy = vals[2].toDouble( &bOk2 );
    a = vals[3].toDouble( &bOk3 );
    if ( bOk1 && bOk2 && bOk3 )
    {
      gradient = QConicalGradient( cx, cy, a );
      // spread type
      if ( vals.count() > 4 )
      {
        QString spread = vals[ 4 ].trimmed().toLower();
        if ( spread == "pad" || spread == "0" )
          gradient.setSpread( QGradient::PadSpread );
        else if ( spread == "repeat" || spread == "2" )
          gradient.setSpread( QGradient::RepeatSpread );
        else if ( spread == "reflect" || spread == "1" )
          gradient.setSpread( QGradient::ReflectSpread );
      }
      // stop points
      QGradientStops stops;
      for ( int i = 5; i < vals.count(); i+=2 )
      {
        bool bOk4, bOk5 = false;
        QColor c;
        double stop = vals[i].toDouble( &bOk4 );
        if ( i+1 < vals.count() )
          bOk5 = stringToColor( vals[ i+1 ], c );
        if ( bOk4 && stop >= 0.0 && stop <= 1.0 && bOk5 && c.isValid() )
          stops.append( QGradientStop( stop, c ) );
      }
      gradient.setStops( stops );
      success = true;
    }
  }
  return success;
}

/*!
  \brief Convert background data to the string representation.
  The resulting string consists of several sub-strings separated by ';' symbol. 
  These sub-strings represent:
  1. background type (enumerator, see Qtx::BackgroundMode)
  2. texture image file name (string)
  3. texture mode (enumerator, see Qtx::TextureMode)
  4. "show texture" flag (boolean)
  5. first color (for simple gradient data) or solid color (for single-colored mode)
  6. second color (for simple gradient data)
  7. type of simple gradient (some integer identifier)
  8. complex gradient data (for custom gradient mode)
  Each sub-string consists of keyword/value couple, in form of "<keyword>=<value>".

  Backward conversion can be done with stringToBackground() method.

  \param bgData background data
  \return string representation of the background data

  \sa stringToBackground()
*/
QString Qtx::backgroundToString( const Qtx::BackgroundData& bgData )
{
  const QString dtSep         = ";";
  const QString kwSep         = "=";
  const QString kwBgType      = "bt";
  const QString kwFileName    = "fn";
  const QString kwTextureMode = "tm";
  const QString kwShowTexture = "ts";
  const QString kwFirstColor  = "c1";
  const QString kwSecondColor = "c2";
  const QString kwGrType      = "gt";
  const QString kwGrData      = "gr";

  Qtx::BackgroundMode bgMode       = bgData.mode();
  QString             fileName;
  Qtx::TextureMode    textureMode  = bgData.texture( fileName );
  bool                showTexture  = bgData.isTextureShown();
  QColor              c1, c2;
  int                 gradientType = bgData.gradient( c1, c2 );
  const QGradient*    gradient     = bgData.gradient();
  QString             grString;
  if ( gradient ) {
    switch ( gradient->type() ) {
    case QGradient::LinearGradient:
      grString = gradientToString( *(static_cast<const QLinearGradient*>( gradient )) );
      break;
    case QGradient::RadialGradient:
      grString = gradientToString( *(static_cast<const QRadialGradient*>( gradient )) );
      break;
    case QGradient::ConicalGradient:
      grString = gradientToString( *(static_cast<const QConicalGradient*>( gradient )) );
      break;
    default:
      break;
    }
  }
  QStringList data;
  data << QString( "%1%2%3" ).arg( kwBgType ).arg( kwSep ).arg( (int)bgMode );
  data << QString( "%1%2%3" ).arg( kwFileName ).arg( kwSep ).arg( fileName );
  data << QString( "%1%2%3" ).arg( kwTextureMode ).arg( kwSep ).arg( (int)textureMode );
  data << QString( "%1%2%3" ).arg( kwShowTexture ).arg( kwSep ).arg( showTexture ? "true" : "false" );
  data << QString( "%1%2%3" ).arg( kwFirstColor ).arg( kwSep ).arg( Qtx::colorToString( c1 ) );
  data << QString( "%1%2%3" ).arg( kwSecondColor ).arg( kwSep ).arg( Qtx::colorToString( c2 ) );
  data << QString( "%1%2%3" ).arg( kwGrType ).arg( kwSep ).arg( gradientType );
  data << QString( "%1%2%3" ).arg( kwGrData ).arg( kwSep ).arg( grString );

  return data.join( dtSep );
}

/*!
  \brief Restore background data from the string representation.

  The string should consist of several sub-strings separated by ';' symbol. 
  Each sub-string consists of keyword/value couple, in form of "<keyword>=<value>".
  The sub-strings can follow in arbitrary order, some keywords can be missing.
  The background data is described by the following values:
  - background type (enumerator, see Qtx::BackgroundMode), keyword "bt"
  - texture image file name (string), keyword "fn"
  - texture mode (enumerator, see Qtx::TextureMode), keyword "tm"
  - "show texture" flag (boolean), keyword "ts"
  - first color (for simple gradient data) or solid color (for single-colored mode), keyword "c1"
  - second color (for simple gradient data), keyword "c2"
  - name of gradient type (string), keyword "gt"
  - complex gradient data (for custom gradient mode), keyword "gr"

  Also, for backward compatibility, background data can be represented by
  single color value, see stringToColor().

  Backward conversion can be done with backgroundToString() method.
  Returns invalid background if conversion could not be done.

  \code
  Qtx::BackgroundData bgData = Qtx::stringToBackground( str );
  if ( bgData.isValid() ) ) doSomething( bgData );
  \endcode

  \param theString string representation of the background data
  \return resulting background data (invalid if conversion has failed)

  \sa backgroundToString()
*/

Qtx::BackgroundData Qtx::stringToBackground( const QString& str )
{
  const QString dtSep         = ";";
  const QString kwSep         = "=";
  const QString kwBgType      = "bt";
  const QString kwFileName    = "fn";
  const QString kwTextureMode = "tm";
  const QString kwShowTexture = "ts";
  const QString kwFirstColor  = "c1";
  const QString kwSecondColor = "c2";
  const QString kwGrType      = "gt";
  const QString kwGrData      = "gr";

  Qtx::BackgroundData bgData = BackgroundData();

  QStringList data = str.split( dtSep, QString::KeepEmptyParts );
  
  QColor c;
  if ( data.count() == 1 && !data.contains( kwSep ) && stringToColor( data[0], c ) ) {
    // solid color mode, for backward compatibility
    bgData.setColor( c );
  }
  else {
    QMap<QString, QString> dmap;
    // background data
    foreach( QString d, data ) {
      QStringList items = d.split( kwSep, QString::KeepEmptyParts );
      if ( items.count() > 0 ) {
	QString kw  = items.takeFirst().trimmed().toLower(); // keyword
	QString val = items.join( kwSep ).trimmed(); // if value contains "=" symbol, we have to restore it
	dmap[ kw ] = val;
      }
    }
    QString bgMode       = dmap.value( kwBgType,      QString() );
    QString fileName     = dmap.value( kwFileName,    QString() );
    QString textureMode  = dmap.value( kwTextureMode, QString() );
    QString showTexture  = dmap.value( kwShowTexture, QString() );
    QString color1       = dmap.value( kwFirstColor,  QString() );
    QString color2       = dmap.value( kwSecondColor, QString() );
    QString gradientType = dmap.value( kwGrType,      QString() );
    QString gradient     = dmap.value( kwGrData,      QString() );
    
    // texture data
    if ( !fileName.isEmpty() || !textureMode.isEmpty() || !showTexture.isEmpty() ) {
      Qtx::TextureMode m = (Qtx::TextureMode)( stringToInt( textureMode,        Qtx::CenterTexture, 
							    Qtx::CenterTexture, Qtx::StretchTexture ) );
      bgData.setTexture( fileName, m );
      QStringList boolvars; boolvars << "true" << "yes" << "ok" << "1";
      if ( boolvars.contains( showTexture.trimmed().toLower() ) )
	bgData.setTextureShown( true );
    }
    QColor c1, c2;
    // try color mode
    bool ok = Qtx::stringToColor( color1, c1 );
    if ( ok ) {
      bgData.setColor( c1 );
    }
    // try simple gradient mode
    ok = Qtx::stringToColor( color2, c2 );
    if ( ok || !gradientType.isEmpty() ) {
      int gt = gradientType.toInt( &ok );
      bgData.setGradient( ok ? gt : -1, c1, c2 );
    }
    // try custom gradient mode
    QLinearGradient  lg;
    QConicalGradient cg;
    QRadialGradient  rg;
    ok = Qtx::stringToLinearGradient( gradient, lg );
    if ( ok ) {
      bgData.setGradient( lg );
    }
    ok = Qtx::stringToRadialGradient( gradient, rg );
    if ( ok ) {
      bgData.setGradient( rg );
    }
    ok = Qtx::stringToConicalGradient( gradient, cg );
    if ( ok ) {
      bgData.setGradient( cg );
    }
    
    // finally set background mode
    Qtx::BackgroundMode m = (Qtx::BackgroundMode)( stringToInt( bgMode,            Qtx::ColorBackground, 
								Qtx::NoBackground, Qtx::CustomGradientBackground ) );
    bgData.setMode( m );
  }

  return bgData;
}

/*!
  \class Qtx::Localizer
  \brief Localization helper

  This helper class can be used to solve the localization problems,
  usually related to the textual files reading/writing, namely when
  floating point values are read / written with API functions.
  The problem relates to such locale specific settings as decimal point
  separator, thousands separator, etc.
  
  To use the Localizer class, just create a local variable in the beginning
  of the code where you need to read / write data from textual file(s).
  The constructor of the class forces setting "C" locale temporarily.
  The destructor switches back to the initial locale.

  There are two ways to create a localizer.
  First constructor accepts category and locale value to be forced as parameters.
  The second constructor does not take parameters, and is just a shortcut to the
  first one, setting LC_NUMERIC as a category and "C" as a locale to force.
  
  \code
  Qtx::Localizer loc;
  readSomething();
  writeSomething();
  \endcode
*/

/*!
  \brief Default constructor. Forces "C" locale to be set as LC_NUMERIC.
*/
Qtx::Localizer::Localizer()
{
  init( LC_NUMERIC, "C" );
}

/*!
  \brief Constructor. Forces \a locale to be set for \a category.
*/
Qtx::Localizer::Localizer( int category, const char* locale )
{
  init( category, locale );
}

/*!
  \brief Internal initialization
  \internal
*/
void Qtx::Localizer::init( int category, const char* locale )
{
  myCategory = category;
  myOriginalLocale = setlocale( category, NULL );
  setlocale( category, locale );
}

/*!
  \brief Destructor. Reverts back to the initial locale.
*/
Qtx::Localizer::~Localizer()
{
  setlocale( myCategory, myOriginalLocale.toLatin1().constData() );
}

/*!
  \class Qtx::BackgroundData
  \brief Stores background data

  This class is used to store background data. Depending on the mode,
  the background can be specified by:
  - image (by assigning the file name to be used as background texture), see setTexture(), setTextureShown()
  - single color (by assigning any color), see setColor()
  - simple two-color gradient (with the gradient type id and two colors), see setGradient( int, const QColor&, const QColor& )
  - complex gradient (by assigning arbitrary gradient data), see setGradient( const QGradient& )

  The class stores all the data passed to it, so switching between different modes can be done
  just by calling setMode() function.

  \note Texture is used with combination of the background mode.

  \note Two-color gradient is specified by two colors and integer identifier. The interpretation of 
  this identifier should be done in the calling code.

  \code
  Qtx::BackgroundData bg;
  bg.setColor( QColor(100, 100, 100) );                  // bg is switched to Qtx::ColorBackground mode
  bg.setGradient( Qt::Horizontal, Qt::gray, Qt::white ); // bg is switched to Qtx::ColorBackground mode
  QLinearGradient grad( 0,0,1,1 ); 
  grad.setColorAt( 0.0, Qt::gray ); 
  grad.setColorAt( 0.5, Qt::white );
  grad.setColorAt( 1.0, Qt::green );
  grad.setSpread( QGradient::PadSpread );
  bg.setGradient( grad );                                // bg is switched to Qtx::CustomGradientBackground mode
  bg.setMode( Qtx::ColorBackground );                    // bg is switched back to Qtx::ColorBackground mode
  bg.setTexture( "/data/images/background.png" );        // specify texture (in the centered mode by default)
  bg.setTextureShown( true );                            // draw texture on the solid color background
  \endcode
*/

/*!
  \brief Default constructor.
  Creates invalid background data.
*/
Qtx::BackgroundData::BackgroundData()
  : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
{
  setMode( Qtx::NoBackground );
}

/*!
  \brief Constructor.
  Creates background data initialized with the specified color
  \param c color
*/
Qtx::BackgroundData::BackgroundData( const QColor& c )
  : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
{
  setColor( c );
}

/*!
  \brief Constructor.
  Creates background data initialized with the specified two-color gradient
  \param type gradient type identifier
  \param c1 first gradient color
  \param c2 second gradient color
  \note the interpretation of the gradient identifier should be done in the calling code
*/
Qtx::BackgroundData::BackgroundData( int type, const QColor& c1, const QColor& c2 )
  : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
{
  setGradient( type, c1, c2 );
}

/*!
  \brief Constructor.
  Creates background data initialized with the arbirtary gradient data
  \param grad gradient data
*/
Qtx::BackgroundData::BackgroundData( const QGradient& grad )
  : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
{
  setGradient( grad );
}

/*!
  \brief Destructor.
*/
Qtx::BackgroundData::~BackgroundData()
{
}

/*!
  \brief Compares two background data objects
*/
bool Qtx::BackgroundData::operator==( const Qtx::BackgroundData& other ) const
{
  return 
    ( myMode         == other.myMode )         && 
    ( myTextureMode  == other.myTextureMode )  &&
    ( myFileName     == other.myFileName )     &&
    ( myColors       == other.myColors )       &&
    ( myGradientType == other.myGradientType ) &&
    ( myGradient     == other.myGradient )     &&
    ( myTextureShown == other.myTextureShown );
}

/*!
  \brief Returns \c false if background data is not set (invalid)
  \return \c true if background data is valid or \c false otherwise
  \sa mode()
*/
bool Qtx::BackgroundData::isValid() const
{
  return myMode != Qtx::NoBackground;
}

/*!
  \brief Get background mode
  \return current background mode
  \sa setMode()
*/
Qtx::BackgroundMode Qtx::BackgroundData::mode() const
{
  return myMode;
}

/*!
  \brief Set background mode
  \param m background mode being set
  \sa mode()
*/
void Qtx::BackgroundData::setMode( const Qtx::BackgroundMode m )
{
  myMode = m;
}

/*!
  \brief Get file name used as a texture image
  \return path to the texture image file
  \sa setTexture(), setTextureShown()
*/
Qtx::TextureMode Qtx::BackgroundData::texture( QString& fileName ) const
{
  fileName = myFileName;
  return myTextureMode;
}

/*!
  \brief Set file name to be used as a texture image.

  \note To show texture image on the background it is necessary to call additionally
  setTextureShown() method.

  \param fileName path to the texture image file name
  \param m texture mode (Qtx::CenterTexture by default)
  \sa texture(), setTextureShown()
*/
void Qtx::BackgroundData::setTexture( const QString& fileName, const Qtx::TextureMode m )
{
  myFileName = fileName;
  myTextureMode = m;
}

/*!
  \brief Check if "show texture" flag is switched on
  \return \c true if "show texture" flag is set or \c false otherwise
  \sa setTextureShown(), texture()
*/
bool Qtx::BackgroundData::isTextureShown() const
{
  return myTextureShown;
}

/*!
  \brief Specify if texture should be shown on the background or no.
  \param on \c true if texture should be shown or \c false otherwise
  \sa isTextureShown(), texture()
*/
void Qtx::BackgroundData::setTextureShown( bool on )
{
  myTextureShown = on;
}

/*!
  \brief Get background color. Returns null QColor if color is not set
  \return solid background color
  \sa setColor(), mode()
*/
QColor Qtx::BackgroundData::color() const
{
  return myColors.count() > 0 ? myColors[0] : QColor();
}

/*!
  \brief Set background color and switch to the Qtx::ColorBackground mode
  \param c color
  \sa color(), mode()
*/
void Qtx::BackgroundData::setColor( const QColor& c )
{
  myColors.clear();
  myColors << c;
  setMode( Qtx::ColorBackground );
}

/*!
  \brief Get simple gradient data.
  Returns -1 and null QColor for \a c1 and \a c2 if gradient data is not set
  \param c1 first gradient color is returned via this parameter
  \param c2 second gradient color is returned via this parameter
  \return current two-colored gradient mode type identifier
  \note the interpretation of the gradient identifier should be done in the calling code
  \sa setGradient(int, const QColor&, const QColor&), mode()
*/
int Qtx::BackgroundData::gradient( QColor& c1, QColor& c2 ) const
{
  c1 = myColors.count() > 0 ? myColors[0] : QColor();
  c2 = myColors.count() > 1 ? myColors[1] : ( myColors.count() > 0 ? myColors[0] : QColor() );
  return myGradientType;
}

/*!
  \brief Set simple background gradient data and switch to the Qtx::SimpleGradientBackground mode
  \param type two-colored gradient mode type identifier
  \param c1 first gradient color is returned via this parameter
  \param c2 second gradient color is returned via this parameter
  \note the interpretation of the gradient identifier should be done in the calling code
  \sa gradient(QColor&, QColor&), mode()
*/
void Qtx::BackgroundData::setGradient( int type, const QColor& c1, const QColor& c2 )
{
  myColors.clear();
  myColors << c1 << c2;
  myGradientType = type;
  setMode( Qtx::SimpleGradientBackground );
}

/*!
  \brief Get complex gradient data.
  Returns QGradient of QGradient::NoGradient if gradient data is not set
  \note This function does not transform simple gradient data set with 
  setGradient( const QString&, const QColor&, const QColor& ) to QGradient class
  \return gradient data
  \sa setGradient(const QGradient&), mode()
*/
const QGradient* Qtx::BackgroundData::gradient() const
{
  return &myGradient;
}

/*!
  \brief Set complex background gradient data and switch to the Qtx::CustomGradientBackground mode
  \param grad gradient data (QLinearGradient, QRadialGradient or QConicalGradient)
  \sa gradient(), mode()
*/
void Qtx::BackgroundData::setGradient( const QGradient& grad )
{
  myGradient = grad;
  setMode( Qtx::CustomGradientBackground );
}

/*!
  \brief Convert string representation of version identifier to the numerical value.
  Resulting value can be used for comparison of different versions (lower, higher, equal).

  String representation of the version consists of zero or more components:

  [major[.minor[.release[patchid]]]]
 
  where
  - major is version major number
  - minor is version minor number
  - release is version release number
  - patchid is a version dev identifier which is one of the following
    * 1 letter optionally followed by 1 or 2 digits, e.g. "a" for "alpha", "b1" for "beta 1"
    * "rc" optionally followed by 1 or 2 digits, e.g. "rc1" for "release candidate 1"
    * "dev" for development version (note: 7.4.0dev > 7.4.0, 7.4.0dev < 7.4.1, 7.4.0dev < 7.4.0a1)

  If version string does not include any component or has invalid format, the function returns 0.

  Examples:
    1.0      - version 1.0
    1.2.3a   - version 1.2.3 alpha
    3.3.3b1  - version 3.3.3 beta 1
    7.4.0rc1 - version 7.4.0 release candidate 1
    7.4.0dev - dev version, i.e. future version 7.4.1 (or 7.5.0)

  \param version string representation of version
  \return numerical identifier of the version
*/
long Qtx::versionToId( const QString& version )
{
  long id = 0;

  QRegExp vers_exp( "^([0-9]+)([A-Z]|RC|DEV)?([0-9]{0,2})$", Qt::CaseInsensitive );
  
  QStringList vers = version.split( ".", QString::SkipEmptyParts );
  int major=0, minor=0;
  int release = 0, dev1 = 0, dev2 = 0;
  if ( vers.count() > 0 ) major = vers[0].toInt();
  if ( vers.count() > 1 ) minor = vers[1].toInt();
  if ( vers.count() > 2 ) {
    if ( vers_exp.indexIn( vers[2] ) != -1 ) {
      release = vers_exp.cap( 1 ).toInt();
      QString tag = vers_exp.cap( 2 ).toLower();
      if ( !tag.isEmpty() ) {
	// patchid is subtracted from version number
	// a   = 55 --> -(55 * 100) + patch number --> 4500..4599, e.g. 7.4.1a1  -> 704004501
	// b   = 54 --> -(54 * 100) + patch number --> 4600..4699, e.g. 7.4.1b1  -> 704004601
	// c   = 53 --> -(53 * 100) + patch number --> 4700..4799, e.g. 7.4.1c1  -> 704004701
	// ...
	// z   = 30 --> -( 1 * 100) + patch number --> 7000..7099, e.g. 7.4.1z1  -> 704007001
	// rc  =  1 --> -( 1 * 100) + patch number --> 9900..9999, e.g. 7.4.1rc1 -> 704009901
	// dev = -1 --> +( 1 * 100) + patch number --> 0100..0199, e.g. 7.4.1dev -> 704010100
	// ---
	// i.e. "a" < "b" < ... < "z" < "rc" < [stable] < "dev"
	if ( tag == "rc" )
	  dev1 = 1;
	else if ( tag == "dev" )
	  dev1 = -1;
	else
	  dev1 = (int)( QChar('z').toLatin1() ) - (int)( tag[ 0 ].toLatin1() ) + 30;
      }
      if ( !vers_exp.cap( 3 ).isEmpty() )
	dev2 = vers_exp.cap( 3 ).toInt();
    }
  }
  
  int dev = dev1*100-dev2;
  id = major;
  id*=100; id+=minor;
  id*=100; id+=release;
  id*=10000;
  id-=dev;

  return id;
}

/*!
  \brief Get Qt installation directory
  
  The function tries to detect qt installation directory by analyzing the system variables in the following order:
  - QT5_ROOT_DIR
  - QT_ROOT_DIR
  - QTDIR

  Optional parameter \a context allows obtaining subdirectory in the Qt installation directory.

  \param context optional sub-directory
  \return path to the Qt installation directory (or its sub-folder, if \a context is specified)
*/

QString Qtx::qtDir( const QString& context )
{

  QStringList vars = { "QT5_ROOT_DIR", "QT_ROOT_DIR", "QTDIR" };
  QString qtPath;
  for (int i = 0; i < vars.length() && qtPath.isEmpty(); i++ ) {
    qtPath = getenv(vars[i]);
  }
  if ( !qtPath.isEmpty() && !context.isEmpty() )
    qtPath = QDir( qtPath ).absoluteFilePath( context );
  return qtPath;
}

/*!
  Creates font from string description
*/
QFont Qtx::stringToFont( const QString& fontDescription )
{
  QFont font;
  if ( fontDescription.trimmed().isEmpty() || !font.fromString( fontDescription ) )
    font = QFont( "Courier", 11 );
  return font;
}

QString Qtx::getenv(const QString & envVar)
{
	QString value;
#ifndef WIN32
	value = qgetenv(envVar.toLocal8Bit().constData());
#else
	LPTSTR buff = new TCHAR[MAX_VALUE_SIZE];
#ifdef UNICODE
	LPTSTR anEnvVar = new TCHAR[envVar.length() + 1];	
	anEnvVar[envVar.toWCharArray(anEnvVar)] = '\0';
#else
	const TCHAR* anEnvVar = envVar.toLocal8Bit(buff).constData();
#endif 
	const DWORD ret = GetEnvironmentVariable(anEnvVar, buff, MAX_VALUE_SIZE);
	buff[ret] = '\0';
	if (ret > 0) {
#ifdef UNICODE
		value = QString::fromWCharArray(buff);
#else
		value = QString::fromLocal8Bit(buff);
#endif 
	}
	delete buff;
#ifdef UNICODE
	delete anEnvVar;
#endif
#endif
	return value;
}

#if !defined WIN32 && !defined __APPLE__ 

#include <X11/Xlib.h>
#include <GL/glx.h>

/*!
  \brief Open the default X display and returns pointer to it.
         This method is available on Linux only.
  \return Pointer to X display.
  \sa getVisual()
*/
void* Qtx::getDisplay()
{
  static Display* pDisplay = NULL;
  if ( !pDisplay )
    pDisplay = XOpenDisplay( NULL );
  return pDisplay;
}

/*!
  \brief Returns pointer to X visual suitable for 3D rendering.
         This method is available on Linux only.
  \return Pointer to X visual.
  \sa getDisplay()
*/
Qt::HANDLE Qtx::getVisual()
{
  Qt::HANDLE res = (Qt::HANDLE)NULL;

  Display* pDisplay = (Display*)getDisplay();
  if ( !pDisplay )
    return res;

  int errorBase;
  int eventBase;

  // Make sure OpenGL's GLX extension supported
  if( !glXQueryExtension( pDisplay, &errorBase, &eventBase ) ){
    qCritical( "Could not find glx extension" );
    return res;
  }

  // Find an appropriate visual

  int doubleBufferVisual[]  = {
    GLX_RENDER_TYPE, GLX_RGBA_BIT, // Needs to support OpenGL
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_ALPHA_SIZE, 1,
    GLX_DEPTH_SIZE, 16,    // Needs to support a 16 bit depth buffer
    GLX_DOUBLEBUFFER, 1,   // Needs to support double-buffering
    GLX_STEREO, 1,         // Needs to support stereo rendering
    GLX_STENCIL_SIZE, 1,
    0x20B2, 1,             // Needs to support srgb profile
    None                   // end of list
  };

  // Try for the double-bufferd visual first
  XVisualInfo *visualInfo = NULL;
  visualInfo = glXChooseVisual( pDisplay, DefaultScreen(pDisplay), doubleBufferVisual );

  if( visualInfo == NULL ){
    qCritical( "Could not find matching glx visual" );
    return res;
  }

  qDebug() << "Picked visual 0x" << hex << XVisualIDFromVisual( visualInfo->visual );
  res = (Qt::HANDLE)( visualInfo->visual );
 
  return res;
}

#endif // WIN32

/*!
  \brief Set default QSurfaceFormat for an application.

  This application property should be set before a creation of the QApplication.
*/  
void Qtx::initDefaultSurfaceFormat()
{
  // Settings from Paraview: 
  // This piece of code was taken from QVTKOpenGLWidget::defaultFormat() method in
  // order to avoid dependency of the SALOME_Session_Server on vtk libraries
  QSurfaceFormat fmt;
  fmt.setRenderableType(QSurfaceFormat::OpenGL);
  fmt.setVersion(3, 2);
  fmt.setProfile(QSurfaceFormat::CoreProfile);
  fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
  fmt.setRedBufferSize(1);
  fmt.setGreenBufferSize(1);
  fmt.setBlueBufferSize(1);
  fmt.setDepthBufferSize(1);
  fmt.setStencilBufferSize(0);
#ifdef WIN32
  fmt.setAlphaBufferSize(0);
#else
  fmt.setAlphaBufferSize(0);
#endif
  fmt.setStereo(false);
  fmt.setSamples(0);
  
  // Settings for OCCT viewer window:
  fmt.setDepthBufferSize(16);
  fmt.setStencilBufferSize(1);
  //  fmt.setProfile(QSurfaceFormat::CompatibilityProfile);

  QSurfaceFormat::setDefaultFormat(fmt);
}

/*!
  \class Qtx::CmdLineArgs
  \brief Get access to the command line arguments in the C-like manner.

  This class translates command line arguments stored in QApplication in form of QStrlingList
  to the char* array, in the same way as they specified to main() function.

  Constructor of class allocates required memory to store arguments; destructor deallocates it,
  This allows using this class as a local variable:

  \code
  Qtx::CmdLineArgs args;
  some_function(args.argc(), args.argv()); // function that has main()-like syntax.
  \endcode
*/

/*!
  \brief Default constructor.
*/
Qtx::CmdLineArgs::CmdLineArgs()
{
  QStringList args = QCoreApplication::arguments();
  myArgc = args.size();
  myArgv = new char*[myArgc];
  for ( int i = 0; i < myArgc; i++ ) {
    QByteArray ba = args[i].toUtf8();
    myArgv[i] = qstrdup(ba.constData());
  }
}

/*!
  \brief Destructor. Deallocates the array with command line arguments
*/
Qtx::CmdLineArgs::~CmdLineArgs()
{
  for ( int i = 0; i < myArgc; i++ )
    delete[] myArgv[i];
  delete[] myArgv;
}

/*!
  \brief Get number of command line arguments
  \return number of arguments
*/
int Qtx::CmdLineArgs::argc() const
{
  return myArgc;
}

/*!
  \brief Get command line arguments
  \return command line arguments
*/
char** Qtx::CmdLineArgs::argv() const
{
  return myArgv;
}
